﻿using HomeManager.Entities;
using HomeManager.Entities.Enums;
using HomeManager.Entities.Extensions;
using HomeManager.Statistics.Entities;
using HomeManager.Statistics.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace HomeManager.Statistics
{
    public class StatisticsManager: IStatisticsManager
    {
        #region Category usage
        public IEnumerable<CategoryStatistics> GetCategoryStatistics(IEnumerable<Operation> operations, OperationType type, CashAccount account, DateTime period = default(DateTime))
        {
            var statistics = new List<CategoryStatistics>();
            var filtered = operations.ByType(account, type).ByMonth(period);
            var currencies = filtered.UsedCurrencies();

            foreach (var currency in currencies)
            {
                var byCurrency = filtered.ByCurrency(currency);
                if(byCurrency.Any())
                    statistics.Add(GetCategoryStatistics(byCurrency, currency));
            }
            return statistics;
        }

        private CategoryStatistics GetCategoryStatistics(IEnumerable<Operation> operations, CurrencyName currency)
        {
            var statistics = new CategoryStatistics();
            var info = new List<CategoryInfo>();
            var categories = operations.UsedCategories();

            foreach (var category in categories)
            {
                info.Add(GetCategoryInfo(operations, category, currency));
            }
            statistics.Currency = currency.ToString();
            statistics.Info = info.OrderByDescending(x => x.Persentage);
            return statistics;
        }

        public IEnumerable<CategoryInfo> GetCategoryInfo(IEnumerable<Operation> operations, OperationCategory category, CashAccount account)
        {
            var info = new List<CategoryInfo>();
            var usedCurrencies = operations.UsedCurrencies();
            foreach (var currency in usedCurrencies)
            {
                var filtered = operations.ByAccount(account).ByCurrency(currency).CurrentMonth();
                if(filtered.Any())
                    info.Add(GetCategoryInfo(filtered, category, currency));
            }
            return info;
        }

        private CategoryInfo GetCategoryInfo(IEnumerable<Operation> operations, OperationCategory category, CurrencyName currency)
        {
            var categorized = operations.ByCategory(category);
            var amountTotal = operations.ByType(category.Type).Sum(x => x.Amount);
            var amountCategorized = categorized.Sum(x => x.Amount);
            var iterations = categorized.Count();
            var persentage = amountTotal > 0 ? amountCategorized * 100 / amountTotal : 0;
            
            return new CategoryInfo()
            {
                Name = category.Name,
                Amount = amountCategorized,
                Iterations = iterations,
                Persentage = Math.Round(persentage, 1),
                BaseCurrency = currency.ToString()
            };
        }
        #endregion

        #region Currency usage
        public IEnumerable<CashInfo> Total(IEnumerable<Operation> operations, OperationType type, CashAccount account)
        {
            var info = new List<CashInfo>();
            var usedCurrencies = operations.UsedCurrencies();
            foreach (var currency in usedCurrencies)
            {
                var filtered = operations.ByType(account, type).ByCurrency(currency);
                if (filtered.Any())
                    info.Add(GetOperationInfo(filtered, currency));
            }
            return info;
        }

        public IEnumerable<CashInfo> TotalWeek(IEnumerable<Operation> operations, OperationType type, CashAccount account)
        {
            var info = new List<CashInfo>();
            var usedCurrencies = operations.UsedCurrencies();
            foreach (var currency in usedCurrencies)
            {
                var filtered = operations.ByType(account, type).ByCurrency(currency);
                filtered = filtered.LastWeek();
                if (filtered.Any())
                    info.Add(GetOperationInfo(filtered, currency));
            }
            return info;
        }

        public IEnumerable<CashInfo> TotalMonth(IEnumerable<Operation> operations, OperationType type, CashAccount account)
        {
            var info = new List<CashInfo>();
            var usedCurrencies = operations.UsedCurrencies();
            foreach (var currency in usedCurrencies)
            {
                var filtered = operations.ByType(account, type).ByCurrency(currency);
                filtered = filtered.CurrentMonth();
                if (filtered.Any())
                    info.Add(GetOperationInfo(filtered, currency));
            }
            return info;
        }

        private CashInfo GetOperationInfo(IEnumerable<Operation> operations, CurrencyName currency)
        {
            var count = operations.ByCurrency(currency).Count();
            var amount = operations.Sum(x => x.Amount);
            return new CashInfo()
            {
                Iterations = count,
                Amount = amount,
                Currency = currency
            };
        }
        #endregion

        #region Earning
        public double GetMonthEarnings(IEnumerable<Operation> operations, CurrencyName currency)
        {
            var collection = operations.ByCurrency(currency);
            var periods = GetPeriods(collection);
            var earnings = new List<double>();
            
            foreach (var period in periods)
            {
                double income = collection.ByType(OperationType.Income).Sum(x => x.Amount);
                double expense = collection.ByType(OperationType.Expense).Sum(x => x.Amount);
                double earning = income - expense;
                earnings.Add(earning);
            }

            double average = earnings.Average();

            return average / earnings.Count();
        }

        private IEnumerable<KeyValuePair<int, int>> GetPeriods(IEnumerable<Operation> operations)
        {
            List<KeyValuePair<int, int>> periods = new List<KeyValuePair<int, int>>();
            foreach (var operation in operations)
            {
                var date = new KeyValuePair<int, int>(operation.Date.Year, operation.Date.Month);
                if (!periods.Contains(date))
                    periods.Add(date);
            }
            return periods;
        }
        #endregion

    }
}
